﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Threading;
using Xiaowei.Services.NativeMessageServiceArgs;
using Log = Logger.Debug;
using Message = System.Windows.Forms.Message;

namespace Xiaowei.Services
{

    namespace NativeMessageServiceArgs
    {
        public enum Modifiers
        {
            Alt = 1,
            Control = 2,
            Shift = 4,
            Windows = 8,
            NoRepeat = 16384
        }

        public delegate void HotkeyDelegate(int id);
        public delegate void MonitorStatusDelegate(bool isOn);
    }

    public interface INativeMessageService
    {
        event HotkeyDelegate HotkeyPressed;
        event MonitorStatusDelegate MonitorStatusChanged;
        event EventHandler SystemSuspended;
        event EventHandler SystemResumed;
        bool RegisterHotKey(int id, Modifiers fsModifiers, System.Windows.Forms.Keys vlc);
    }

    internal class NativeMessageService : System.Windows.Forms.NativeWindow, INativeMessageService
    {
        private static class NativeMessage
        {
            public const int WM_HOTKEY = 0x0312;
            public const int WM_DESTROY = 0x0002;
            //power related
            public const int WM_POWERBROADCAST = 0x0218;
            public const int PBT_POWERSETTINGCHANGE = 0x8013;

            public const int PBT_APMSUSPEND = 0x4;
            public const int PBT_APMRESUMESUSPEND = 0x7;
            public const int PBT_APMRESUMEAUTOMATIC = 0x12;

            public const int DEVICE_NOTIFY_WINDOW_HANDLE = 0x00000000;

            public static Guid GUID_MONITOR_POWER_ON = Guid.Parse("02731015-4510-4526-99E6-E5A17EBD1AEA");
            public static Guid GUID_CONSOLE_DISPLAY_STATE = Guid.Parse("6FE69556-704A-47A0-8F24-C28D936FDA47");

            [StructLayout(LayoutKind.Sequential, Pack = 4)]
            public struct POWERBROADCAST_SETTING
            {
                public Guid PowerSetting;
                public uint DataLength;
                public byte Data;
            }

            [DllImport("user32.dll")]
            public static extern bool RegisterHotKey(IntPtr hWnd, int id, int fsModifiers, int vlc);
            [DllImport("user32.dll")]
            public static extern bool UnregisterHotKey(IntPtr hWnd, int id);

            [DllImport(@"User32", SetLastError = true, EntryPoint = "RegisterPowerSettingNotification", CallingConvention = CallingConvention.StdCall)]
            private static extern IntPtr RegisterPowerSettingNotification(IntPtr hRecipient, ref Guid PowerSettingGuid, Int32 Flags);

            [DllImport(@"User32", SetLastError = true, EntryPoint = "UnregisterPowerSettingNotification", CallingConvention = CallingConvention.StdCall)]
            private static extern bool UnregisterPowerSettingNotification(IntPtr handle);


            [DllImport(@"User32", SetLastError = true, EntryPoint = "RegisterSuspendResumeNotification", CallingConvention = CallingConvention.StdCall)]
            private static extern IntPtr RegisterSuspendResumeNotification(IntPtr hRecipient, Int32 Flags);

            [DllImport(@"User32", SetLastError = true, EntryPoint = "UnregisterSuspendResumeNotification", CallingConvention = CallingConvention.StdCall)]
            private static extern bool UnregisterSuspendResumeNotification(IntPtr handle);

            private static readonly List<IntPtr> Notifies = new List<IntPtr>();
            public static void RegisterPowerSettingNotification(IntPtr hwnd, Guid evt)
            {
                var n = RegisterPowerSettingNotification(hwnd, ref evt, DEVICE_NOTIFY_WINDOW_HANDLE);
                Notifies.Add(n);
            }

            public static void UnregisterPowerSettingNotification()
            {
                foreach (var item in Notifies)
                {
                    UnregisterPowerSettingNotification(item);
                }
            }

            private static IntPtr suspendNotifier;

            public static void RegisterSuspendResumeNotification(IntPtr hwnd)
            {
                suspendNotifier = RegisterSuspendResumeNotification(hwnd, DEVICE_NOTIFY_WINDOW_HANDLE);
            }

            public static void UnregisterSuspendResumeNotification()
            {
                UnregisterSuspendResumeNotification(suspendNotifier);
            }
        }
        private static class WindowMessage
        {
            public delegate void MessageHandler(Message m);

            private static readonly Dictionary<int, MessageHandler> msgMap = new Dictionary<int, MessageHandler>();
            public static void AddHandler(int id, MessageHandler handler)
            {
                msgMap.Add(id, handler);
            }

            public static void RemoveHandler(int id)
            {
                msgMap.Remove(id);
            }

            public static void Run(int id, Message m)
            {
                if (msgMap.ContainsKey(id))
                {
                    msgMap[id]?.Invoke(m);
                }
            }
        }
        private readonly HashSet<int> KeysHashSet = new HashSet<int>();
        public event HotkeyDelegate HotkeyPressed;
        public event MonitorStatusDelegate MonitorStatusChanged;
        public event EventHandler SystemSuspended;
        public event EventHandler SystemResumed;
        private Dispatcher Dispatcher { get; } = Dispatcher.CurrentDispatcher;
        public NativeMessageService()
        {
            this.CreateHandle(new System.Windows.Forms.CreateParams());

            InitWindowMessge();

            Application.Current.Dispatcher.Invoke(()=>
            {
                Application.Current.Exit += (s, e) =>
{
                UnregisterHotKey();
                NativeMessage.UnregisterPowerSettingNotification();
                NativeMessage.UnregisterSuspendResumeNotification();
            };
            });
        }

        private void InitWindowMessge()
        {
            //enable
            NativeMessage.RegisterPowerSettingNotification(this.Handle, NativeMessage.GUID_MONITOR_POWER_ON);
            NativeMessage.RegisterSuspendResumeNotification(this.Handle);

            //handle
            WindowMessage.AddHandler(NativeMessage.WM_DESTROY, (m) =>
            {
                UnregisterHotKey();
                NativeMessage.UnregisterPowerSettingNotification();
                NativeMessage.UnregisterSuspendResumeNotification();
            });

            WindowMessage.AddHandler(NativeMessage.WM_HOTKEY, (m) =>
            {
                HotkeyPressed?.Invoke(m.WParam.ToInt32());
            });

            WindowMessage.AddHandler(NativeMessage.WM_POWERBROADCAST, (m) =>
            {
                if (m.WParam.ToInt32() == NativeMessage.PBT_POWERSETTINGCHANGE)
                {
                    var s = (NativeMessage.POWERBROADCAST_SETTING)Marshal.PtrToStructure(m.LParam, typeof(NativeMessage.POWERBROADCAST_SETTING));
                    if (s.PowerSetting == NativeMessage.GUID_CONSOLE_DISPLAY_STATE)
                    {
                        Log.WriteLine($"GUID_CONSOLE_DISPLAY_STATE, {s.Data}");
                        MonitorStatusChanged?.Invoke(s.Data > 0);
                    }
                    else if (s.PowerSetting == NativeMessage.GUID_MONITOR_POWER_ON)
                    {
                        Log.WriteLine($"GUID_MONITOR_POWER_ON, {s.Data}");

                        MonitorStatusChanged?.Invoke(s.Data > 0);
                    }
                }
                else if (m.WParam.ToInt32() == NativeMessage.PBT_APMSUSPEND)
                {
                    Log.WriteLine($"PBT_APMSUSPEND");
                    SystemSuspended?.Invoke(this.Handle, null);
                }
                else if (m.WParam.ToInt32() == NativeMessage.PBT_APMRESUMESUSPEND)
                {
                    Log.WriteLine($"PBT_APMRESUMESUSPEND");
                    SystemResumed?.Invoke(this.Handle, null);
                }
                else if (m.WParam.ToInt32() == NativeMessage.PBT_APMRESUMEAUTOMATIC)
                {
                    Log.WriteLine($"PBT_APMRESUMEAUTOMATIC");
                }
            });
        }

        public bool RegisterHotKey(int id, Modifiers fsModifiers, System.Windows.Forms.Keys vlc)
        {
            return Dispatcher.Invoke
                (
                    () =>
                    {
                        Log.WriteLine($"RegisterHotKey, id:{id}, Modifiers:{fsModifiers}, Keys:{vlc}");
                        if (KeysHashSet.Contains(id))
                        {
                            if (NativeMessage.UnregisterHotKey(this.Handle, id))
                            {
                                KeysHashSet.Remove(id);
                            }
                            else
                            {
                                Log.WriteLine($"UnregisterHotKey failed, id:{id}");
                            }

                        }
                        if (NativeMessage.RegisterHotKey(this.Handle, id, (int)fsModifiers, (int)vlc))
                        {
                            KeysHashSet.Add(id);
                            Log.WriteLine($"RegisterHotKey, id:{id}, Modifiers:{fsModifiers}, Keys:{vlc}, success!");
                            return true;
                        }
                        else
                        {
                            Log.WriteLine($"RegisterHotKey, id:{id}, Modifiers:{fsModifiers}, Keys:{vlc}, failed.");
                            return false;
                        }
                    }
                );

        }

        private void UnregisterHotKey()
        {
            foreach (int id in KeysHashSet)
            {
                NativeMessage.UnregisterHotKey(this.Handle, id);
            }
        }

        protected override void WndProc(ref System.Windows.Forms.Message m)
        {
            WindowMessage.Run(m.Msg, m);
            base.WndProc(ref m);
        }
    }

}
